#!/bin/sh
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# PASST - Plug A Simple Socket Transport
#  for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
#  for network namespace/tap device mode
#
# test/lib/perf_report - Prepare JavaScript report for performance tests
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>

PERF_INIT=0
PERF_LINK_COUNT=0
PERF_JS="${LOGDIR}/web/perf.js"

PERF_TEMPLATE_HTML="document.write('"'
Throughput in Gbps, latency in µs. Threads are <span style="font-family: monospace;">iperf3</span> threads, <i>passt</i> and <i>pasta</i> are currently single-threaded.<br/>
Click on numbers to show test execution. Measured at head, commit <span style="font-family: monospace;">__commit__</span>.

<style type="text/CSS">
table.passt td { border: 0px solid; padding: 6px; line-height: 1; }
table.passt td { text-align: right; }
table.passt th { text-align: center; font-weight: bold; }
table.passt tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
table.passt tr:nth-child(3n+0) { background-color: #112315; }
table.passt tr:not(:nth-child(3n+0)) td { background-color: #101010; }
table.passt td:nth-child(6n+7) { background-color: #603302; }
table.passt tr:nth-child(1) { background-color: #363e61; }
td:empty { visibility: hidden; }
</style>

<ul>
<li><p>passt</p>
<table class="passt" width="70%">
	<tr>
		<th/>
		<th id="perf_passt_tcp" colspan="__passt_tcp_cols__">TCP, __passt_tcp_threads__ at __passt_tcp_freq__ GHz</th>
		<th id="perf_passt_udp" colspan="__passt_udp_cols__">UDP, __passt_udp_threads__ at __passt_udp_freq__ GHz</th>
	</tr>
	<tr>
		<td align="right">MTU:</td>
		__passt_tcp_header__
		__passt_udp_header__
	</tr>
	__passt_tcp_LINE__ __passt_udp_LINE__
</table>

<style type="text/CSS">
table.pasta_local td { border: 0px solid; padding: 6px; line-height: 1; }
table.pasta_local td { text-align: right; }
table.pasta_local th { text-align: center; font-weight: bold; }
table.pasta_local tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
table.pasta_local tr:nth-child(3n+0) { background-color: #112315; }
table.pasta_local tr:not(:nth-child(3n+0)) td { background-color: #101010; }
table.pasta_local td:nth-child(4n+2) { background-color: #603302; }
table.pasta_local tr:nth-child(1) { background-color: #363e61; }
table.pasta td { border: 0px solid; padding: 6px; line-height: 1; }
table.pasta td { text-align: right; }
table.pasta th { text-align: center; font-weight: bold; }
table.pasta tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
table.pasta tr:nth-child(3n+0) { background-color: #112315; }
table.pasta tr:not(:nth-child(3n+0)) td { background-color: #101010; }
table.pasta td:nth-child(4n+5) { background-color: #603302; }
table.pasta tr:nth-child(1) { background-color: #363e61; }
td:empty { visibility: hidden; }
</style>

</li><li><p>pasta: local connections/traffic</p>
<table class="pasta_local" width="70%">
	<tr>
		<th/>
		<th id="perf_pasta_lo_tcp" colspan="__pasta_lo_tcp_cols__">TCP, __pasta_lo_tcp_threads__ at __pasta_lo_tcp_freq__ GHz</th>
		<th id="perf_pasta_lo_udp" colspan="__pasta_lo_udp_cols__">UDP, __pasta_lo_udp_threads__ at __pasta_lo_udp_freq__ GHz</th>
	</th>
	<tr>
		<td align="right">MTU:</td>
		__pasta_lo_tcp_header__
		__pasta_lo_udp_header__
	</tr>
	__pasta_lo_tcp_LINE__ __pasta_lo_udp_LINE__
</table>

</li><li><p>pasta: connections/traffic via tap</p>
<table class="pasta" width="70%">
	<tr>
		<th/>
		<th id="perf_pasta_tap_tcp" colspan="__pasta_tap_tcp_cols__">TCP, __pasta_tap_tcp_threads__ at __pasta_tap_tcp_freq__ GHz</th>
		<th id="perf_pasta_tap_udp" colspan="__pasta_tap_udp_cols__">UDP, __pasta_tap_udp_threads__ at __pasta_tap_udp_freq__ GHz</th>
	</tr>
	<tr>
		<td align="right">MTU:</td>
		__pasta_tap_tcp_header__
		__pasta_tap_udp_header__
	</tr>
	__pasta_tap_tcp_LINE__ __pasta_tap_udp_LINE__
</table>

</li></ul>'

PERF_TEMPLATE_JS="');

var perf_links = [
"

PERF_TEMPLATE_POST='];

for (var i = 0; i < perf_links.length; i++) {
	var obj = document.getElementById(perf_links[i][0]);

	obj.addEventListener("click", function(event) {
		var ci_video = document.getElementById("ci");
		var top = ci_video.offsetTop - 5;
		var seek;

		for (var i = 0; i < perf_links.length; i++) {
			if (this.id == perf_links[i][0]) {
				seek = perf_links[i][1];
			}
		}

		event.preventDefault();
		ci_player.dispose();
		ci_player = AsciinemaPlayer.create("/builds/latest/web/ci.cast",
						   ci_video,
						   { cols: 240, rows: 51, poster: "npt:999:0", startAt: seek, autoplay: true });

		window.scrollTo({ top: top, behavior: "smooth" })
	}, false);
}
'

# perf_init() - Process first part of template
perf_init() {
        mkdir -p "$(dirname "${PERF_JS}")"
	echo "${PERF_TEMPLATE_HTML}" > "${PERF_JS}"
	perf_report_sub commit "$(echo ${COMMIT} | sed "s/'/\\\'/g")"
	PERF_INIT=1
}

# perf_fill_lines() - Fill multiple "LINE" directives in template, matching rows
perf_fill_lines() {
	while true; do
		__file_line="$(sed -n '/__.*_LINE__/{=;q}' "${PERF_JS}")"
		[ -z "${__file_line}" ] && break

		__line_no=0
		__done=0
		__line_buf="<tr>"
		while true; do
			__match_first_td=0
			for __t in $(sed -n '/__.*_LINE__/{p;q}' "${PERF_JS}"); do
				if [ ${__match_first_td} -eq 1 ]; then
					__matching_line_no=0
					while true; do
						__line_part=
						__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__matching_line_no}"'/p')"
						[ -z "$(eval echo \$${__var_name})" ] && break
						__line_part="$(eval echo \$${__var_name})"
						__td_check="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
						if [ "${__td_check}" = "${__td_match}" ]; then
							__line_part="$(echo "${__line_part}" | sed -n 's/^<td>[^>]*<\/td>\(.*\)$/\1/p')"
							break
						fi
						__matching_line_no=$((__matching_line_no + 1))
					done
				else
					__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__line_no}"'/p')"
					[ -z "$(eval echo \$${__var_name})" ] && __done=1 && break
					__line_part="$(eval echo \$${__var_name})"
					__td_match="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
				fi
				__line_buf="${__line_buf}${__line_part}"
				__match_first_td=1
			done
			[ ${__done} -eq 1 ] && break
			__line_no=$((__line_no + 1))
			__line_buf="${__line_buf}</tr><tr>"
		done
		__line_buf="${__line_buf}</tr>"
		__line_buf="$(printf '%s\n' "${__line_buf}" | sed -e 's/[]\/$*.^[]/\\&/g')"
		sed -i "${__file_line}s/.*/${__line_buf}/" "${PERF_JS}"
	done
}

# perf_finish() - Add trailing backslashes and process ending templates
perf_finish() {
	PERF_INIT=0
	perf_fill_lines
	sed -i 's/^.*$/&\\/g' "${PERF_JS}"
	echo "${PERF_TEMPLATE_JS}" >> "${PERF_JS}"
	echo "${PERF_TEMPLATE_POST}" >> "${PERF_JS}"
}

# perf_report_sub() - Apply simple substitutions in template
perf_report_sub() {
	__et="$(printf '%s\n' "${1}" | sed -e 's/[\/&]/\\&/g')"
	__es="$(printf '%s\n' "${2}" | sed -e 's/[]\/$*.^[]/\\&/g')"

	sed -i 's/__'"${__et}"'__/'"${__es}"'/g' "${PERF_JS}"
}

# perf_report_append_js() - Append generic string to current template buffer
perf_report_append_js() {
	PERF_TEMPLATE_JS="${PERF_TEMPLATE_JS}${@}"
}

# perf_report() - Start of single test report
perf_report() {
	__mode="${1}"
	__proto="${2}"
	__threads="${3}"
	__freq="${4}"

	REPORT_IN="${__mode}_${__proto}"

	[ ${__threads} -eq 1 ] && __threads="one thread" || __threads="${__threads} threads"
	perf_report_sub "${__mode}_${__proto}_threads" "${__threads}"
	perf_report_sub "${__mode}_${__proto}_freq" "${__freq}"

	perf_report_append_js "[ 'perf_${__mode}_${__proto}', $(video_time_now) ],"
}

# perf_th() - Table header for a set of tests
perf_th() {
	[ ${PERF_INIT} -eq 0 ] && return

	shift

	__th_buf=
	__cols_count=0
	for __arg; do
		__th_buf="${__th_buf}<td>${__arg}</td>"
		__cols_count=$((__cols_count + 1))
	done
	perf_report_sub "${REPORT_IN}_header" "${__th_buf}"
	perf_report_sub "${REPORT_IN}_cols" ${__cols_count}
}

# perf_tr() - Main table row
perf_tr() {
	[ ${PERF_INIT} -eq 0 ] && return

	__line_no=0
	shift
	while true; do
		[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
		__line_no=$((__line_no + 1))
	done
	eval ${REPORT_IN}_LINE_${__line_no}="\"<td>${@}</td>\""
}

# perf_td() - Single cell with test result
perf_td() {
	[ ${PERF_INIT} -eq 0 ] && return

	__rewind="${1}"
	shift

	__line_no=0
	while true; do
		[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
		__line_no=$((__line_no + 1))
	done
	__line_no=$((__line_no - 1))
	[ -z "${1}" ] && __id=0 || __id="perf_${PERF_LINK_COUNT}"
	eval ${REPORT_IN}_LINE_${__line_no}=\""\${${REPORT_IN}_LINE_${__line_no}}<td id=\"${__id}\">${1}</td>"\"
	[ -z "${1}" ] && return

	perf_report_append_js "[ '${__id}', $(($(video_time_now) - ${__rewind})) ],"
	PERF_LINK_COUNT=$((PERF_LINK_COUNT + 1))
}

# perf_te() - End of a table, currently unused
pert_te() {
	:
}
